home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC Media 22
/
PC MEDIA CD22.iso
/
share
/
prog
/
datalib2
/
database.txt
< prev
next >
Wrap
Text File
|
1995-08-16
|
84KB
|
2,336 lines
C++ Database Library
Version 2.3, 16th August 1995
Package Contents
================
The package contains the following files:
SOURCE.ZIP The source files for the library.
This file contains the following files:
DATABASE.CPP Source file
DATAEXE1.CPP Source file
DATAEXE2.CPP Source file
FIELD.CPP Source file
INDCLUS.CPP Source file
INDEX.CPP Source file
INDEX2.CPP Source file
RECORD.CPP Source file
UTIL.CPP Source file
EXOP.HPP Private header file
DATAPRIV.HPP Private header file
DATABASE.HPP Header file for all versions of the library
DATABASE.TXT User manual for library.
README.TXT File list and conditions
DBRPT.CPP Example program
FAMILYS.DBF Example database
FAMILYS.CPP Example program
FAMILYS.NDX Index file for example database
PACKDB.CPP Example program
Library, Conditions for use:
============================
This library of routines is provided as shareware. You may try the
functions defined in the library within in your own programs, however
if you decide to incorporate them permanently then you are obliged to
register your copy of the software. Registration costs ú30 UK sterling
or $40.00 (by cheque) to:
Robin Abbott Compuserve ID 100023,535
37 Plantation Drive,
Christchurch,
Dorset
ENGLAND
BH23 5SG
Compuserve Registration
=======================
To register this library through Compuserve GO SWREG, and follow the
instructions there.
Use registration number 7118. Compuserve ID 100023,535
Registration brings you the next library upgrade free of charge and
technical support.
Compiled programs which incorporate the code within these libraries
may be distributed with no further royalty.
The library may be freely distributed provided that all files are
included.
Known Bugs, 15/8/95
===================
I have now tested the index routines with 100,000 record databases,
generating and changing every single record in the database.
I am (fairly!) confident that all the significant bugs have been
ironed out, as these tests cover index tree depths of up to 6.
The verifyindex() routine has been made much more stringent and
shows valid index structures for all the tests I have carried out.
Bug reports
===========
Send bug reports to the author. I will try to solve bugs and either
return an updated software version asap, or provide a work around.
Simple bugs can just be reported, e.g. the bug that field names
could not have an underscore in them within the expression
evaluator.
More complex bugs such as all the errors that occurred in the index
routines really need to identify the problem on a specific database.
This may not be easy, but please try and identify the routines which
cause the problem and send the complete database.
Introduction
============
This library is intended to allow Borland C++ programs to create,
write, interrogate and manipulate databases and indexes which are in
dBase III format. These files usually have the extension .dbf. The
library is based on a group of objects which represent databases,
records and fields.
The library of functions will be of use in building database
applications and report generators, both standalone, and for use with
other database applications.
Using the Library in Building Programs
======================================
The package incorporates 9 source files, which must be included in
the project build, or which can be built into a library. These files
are as follows:
DATABASE.CPP Source file (X)
DATAEXE1.CPP Source file (X)
DATAEXE2.CPP Source file (X)
FIELD.CPP Source file (X)
INDCLUS.CPP Source file (X)
INDEX.CPP Source file (X)
INDEX2.CPP Source file (X)
RECORD.CPP Source file (X)
UTIL.CPP Source file (X)
These incorporate all the functions described in this
document and should be compiled using the large model. Copy
the header file database.hpp to your include directory.
Programs which use the library must include the header file
database.hpp, and must be compiled using the large model.
Using the Package
=================
The package is intended to be as easy as possible to use. A database
object should be opened with the name of the database in which the
user is interested together with the first index file if required.
Additional index files may be added as required. A record object (or
many records) may now be opened on this database. The record object
may be positioned at any record in the file and the fields in the
record may be examined or modified. It is possible to select records
on a variety of criteria including dBase expressions.
The following example shows a program which opens the file "CLUB.DBF"
and swaps the month and day for each record in the file. This was
written to correct an error in importing a file in American date
format. The file has a number of fields, the only one considered here
is the DOB field - the date of birth which was incorrectly imported.
/*
Short Program to exchange Day & Month for every record in a
database
*/
#include <string.h>
#include <database.hpp>
#include <stdio.h>
#include <stdlib.h>
void main()
{
char ws[128]; // Convenient Workspace
database db("CLUB"); // Open database, no index
record rec(db); // Record open on database
int fn=db.getfield("DOB")->getnumber(); // Get number of DOB field
int sel=rec.select(FIRST); // Select first record
while(!sel) // For every record in the file
{
int d,m,y; // day, month and year
gnums(rec.getfield(fn),d,m,y); // Get date of birth to d,m,y
sprintf(ws,"%04d%02d%02d",y,d,m); // Print in db form, swap d&m
rec.setfield(fn,ws); // Update field
rec.write(); // Write back to disk
sel=rec.select(NEXT); // Select next record
}
}
Indexes
=======
The package is capable of handling dBase style NDX files (indexes).
These are attached using the database::addindex() function. Indexes
are referred to by the file name with no path and no extension. Thus
the index "c:\c\name.ndx" is referred to using the string "name".
Indexes are usually attached when the database is opened, or when
they have been created. New records will be included in the index
only if the index is attached to the database. Should an out of date
index be attached to a database then there is a risk that the records
will be out of order, to check a database the database::verifyindex()
function may be used to check the index.
Big Files
=========
Large databases (greater than about 2000 records) need handling
carefully to avoid speed problems. Indexes should be built on these
databases as little as possible, being maintained by attaching them
to the database. Selecting records should be done by using the index
key (the record::selkey() function), which will be considerably
faster than using any of the record::select() functions - these
functions search the whole database from beginning to end. The
database::verifyindex() function should also be avoided as this
searches the entire database.
Errors
======
The database will report fatal errors (by printing the errors on
screen) which may cause a crash such as opening a record on an
invalid database. Some more trivial errors which may be trapped by
the calling program will also be printed. The global variable eroff
should be set to 1 if it is required to turn off printing of these
non-fatal errors. Fatal errors are printed using printf for DOS
compilations, and using MessageBox() for Windows compilations.
Objects
=======
There are three types of object used by application programs in the
database library.
Database objects, Field Objects and Record Objects. A database object
holds the description of a database, field objects associated with the
database hold the definitions of the fields in the database, and
record objects hold all the information on a record.
Assignment of Objects (Use of the = Operator)
=============================================
The = operator may be used within the database library only to copy
one record to another (within the same database), this allows the
user to save the position and contents of a record within the
database, the = operator is implemented to ensure that a complete
copy of all structures and variables associated with the record is
made. An example of the use of this is shown above in the subtotals
program.
The = operator must not be used with database objects or field
objects, or between records on different databases. See the example
program DBPACK below to see how a record may be copied between
different databases.
DATABASE Objects
================
A database object is constructed with the name of the database to be
opened, and an optional index file. The file is opened and the user
then has the capability to determine the state of the file and the
fields in it. Constructing a database object automatically creates a
linked list of the fields in the database.
To create a new database a database object is opened without a file
name, and then fields of any type can be added to the database. When
all the database fields have been written the database can be
established by saving it with a filename after which records may be
created within it.
The database object has the following public interface:
class database
{
public :
char ers[81]; // Reports the error in an expression
database(char *name,char *index=0); // Construct with file+index
database(void); // Construct a new database
~database(void);
int addfield(field *newfield); // Copy field to new database
int addfield(char *nm,int l=1,int rdp=0); // Add field to new
// database
int addindex(char *fname); // Add index to the database
int buildindex(char *expr,char *fn,
void (*prog)(int,long)=0,
int ord=100); // Build a newindex
field *getfield(int n)
{return(fielda[n-1]);} // Return field n, 1<=n<=nfield
field *getfield(char *name); // Get ptr to field by name
char *getindkey(char *name); // Get index key
int getindtype(char *name); // Get index type, OPINT,OPSTR
long getnrec(void) {return(nrec);} // Number of records
int getreclen(void) {return(reclen);} // Total record length
int getnfield(void) {return(nfield);} // Number of fields
int getversion(int &maj,int &min); // Get library version
int isvalid(void) {return(valid);} // Return valid flag
int verifyindex(char *iname,int depth=1,
void (*progress)(int test,long recnum)=0,
int order=100); // Verify an index
int setdateformat(dates form); // Set date format
int subindex(char *iname); // Subtract an index
}
FIELD Objects
=============
A field object is created to enable the functions associated with the
database to determine the format of the records. It also allows the
user to determine the type, name and parameters of any field. The
field type has the following public interface:
class field
{
public:
field(int number,char *name,char type,int length,int rdp,
int recpos);
~field(void);
// Accessor functions
int getnumber(void) {return(number);} // Get no. of field in record
char *getname(void) {strcpy(namecop,name);
return(namecop);} // Get name of field
char gettype(void) {return(type);} // Get type of field
int getlen(void) {return(len);} // Get length of field
int getrdp(void) {return(rdp);} // Get right of dp of field
};
RECORD Objects
==============
There may be any number of record objects associated with each
database object. Each record object holds one record in the file, and
it is possible to load a record object with the first, last, next,
previous, or any numbered record in the database. The record object
has the following public interface:
class record
{
public:
record(class database &db,char *indname=0);
~record(void);
void operator=(record &s); // Copy record entirely
int eval(char *expr,void *result,int &rtype); // Evaluate expression
int eval(void *result,int &rtype); // Evaluate prev. expression
int indchk(char *exp,int &rtype); // check an index expression
char *indname(); // Return index name
int seldbf(long n); // Select record in dbf
int select(long n,int type=NOTDEL,int df=0); // Select rec number
int select(int field,int val,long n,
int type=NOTDEL); // Select fn==val
int select(int field,char *s,long n,
int type=NOTDEL); // Select field==s
int select(int field,void *s,
int (*comp)(void *,void *),
long n,int type=NOTDEL); // User def select
int select(char *expr,long n,
int type=NOTDEL); // Select when expr true
char *getfield(int n,int tf=1); // Get field by number
char *getfield(char *name,int tf=1); // Get field by name
long getrecnum() {return(rn);} // Current record number
int getdelstate() {return(delstate);} // Delete state of record
void setdelstate(int sval); // Set delete state of record
int selkey(char *value,int type=NOTDEL,
int num=OPSTR); // Select by key
int selkey(double value,int type=NOTDEL);
int selkey(); // Find other records by key
int setfield(char *name,char *value); // Set new value for field name
int setfield(char *name,float value);
int setfield(int number,char *value); // Set new value for field by #
int setfield(int number,float value);
int write(int type=OVER); // Write rec to dbf
};
Representation of Field Types
=============================
Field types in dBase are Numeric (N), Character (C), Date (D),
Logical (L) or Memo (M). They are held internally in the follwoing
forms:
Numeric
-------
Numeric fields are held in double (8 byte) form. When values are
returned from the database they are in ASCII form (e.g. 1.235). Thus
the C atoi() or atof() functions should be used to convert to integer
or floating form as required.
Character
---------
Character fields are held internally as normal C format 0 terminated
strings.
Logical
-------
Logical fields are held as a single character string (0 terminated)
where the value is "T","t","Y", or "y" for true values and
"F","f","N", or "n" for false values.
Date
----
Date fields are held as character strings (0 terminated) of length 8.
These are in the form YYYYMMDD, thus 5th November 1962 is held as
"19621105".
Memo
----
Memo fields are held as character strings which are 0 terminated,
they may be any length up to 64000 bytes provided there is sufficient
memory to hold them. Internally memos read from a database are held
in a buffer which starts at 512 bytes, and is expanded when necessary
to read a larger memo.
Expression Evaluator
====================
The expression evaluator in the database library provides a complete
dBase compatible expression evaluation function which may be used to
select records, may be used for calculations on records, or is used
to create index expressions. The expression evaluator is associated
with the database object, and there is a public string (ers in the
database object) which holds the ASCII reperesentation of the last
database error which occurred, see the record::eval function for an
example.
It provides the following operators which are shown in operator
precedence (those at the top of the list are executed first), note
that the dBase =< and => (which in dBase are equivalent to >= and <=)
are not implemented in the library (use the <= and >= operators
instead).
() Parenthesis bracket operations and change the order of
evaluation.
+ - Unary + and - operate on a single number.
** ^ Exponentiation (to the power of)
* / Multiplication, Division
+ - Add and Subtract, Numbers, Dates or Strings
Addition of two strings concatenates them. Subtraction on
strings takes the spaces from the end of the first string,
concatenates the second string to the first string, and then
puts the spaces back at the end of the new string.
A date may have a number added or subtracted from it which
provides a date which is that many days in the future or
past. A date may be subtracted from another date to give the
number of days between the two dates.
<
>
=
<> or #
<=
>= These are relational operators and act on strings, dates, or
numbers to indicate if the first item is less than (<), less
than or equal to (<=), equal to (=), greater than (>),
greater than or equal (>=) or not equal (<>, or #) to the
second item. The first item must be of the same type as the
second item.
$ This is a string operator used in the form A$B, it returns
true if A is identical to B, or is contained within it.
.AND. Logical AND.
.OR. Logical OR.
.NOT. Logical NOT, note the lower priority than .AND. and .OR.
which can be confusing to those used to conventional operator
precedence.
Field names be used wherever a number, string or date may be used.
e.g.
database db("Names");
record rec(db);
rec.select("upper(name)=\"B\"",FIRST); // Select 1st
record where 1st character of name field is B
rec.select("ageyears+agemonths/12<12.5",NEXT); // Select
next record where age is less than 12.5
rec.select("dob=ctod(\"17/4/78\")",FIRST); // Select
1st record where dob field is 17th April 1978.
Expression Evaluator functions
==============================
The following functions are available:
ABS(numeric expr) Gives the absolute value of the supplied
expression.
ASC(string expr) Returns decimal ASCII value of the first
character of string
AT(char s1,char s2); If string s1 is to be found in string s2 then
this returrns the position that it is to be
found at (starting at 1). it returns 0 if s2
is not to be found in s1.
CDOW(date expr) Returns a string which names the day of the
week supplied by date expr.
CHR(numeric expr) Gives a string of length 1 holding the
character which has the ASCII code supplied
by the numeric expression.
CMONTH Returns a string which names the month
supplied by date expr.
CTOD(char expr) Returns a date matching the character string
supplied as input.
DATE() Returns the current operating system date.
DAY(date expr) Returns the number of the day from the
supplied date expression.
DOW(date expr) Returns a number representing the day of the
week from the supplied date expression.
DTOC(date expr) Converts the date expression to a charcter
string in system date format.
DTOS(date expr) Gives the dBase date string form of date expr
(YYYYMMDD). Note this function is dBase IV,
not dBase III.
EXP(numeric expr) Gives the result of e to the power numeric
expr.
IIF(condition,expr1,expr2) If condition is true returns the result
of evaluating expr1, otherwise it returns the
result of evaluating expr2.
INT(numeric expr) Gives the integer value of the supplied
expression.
ISALPHA(char expr) Evaluates to TRUE if the 1st character of the
supplied expression is a letter of the
alphabet, FALSE if not.
ISDIGIT(char expr) Evaluates to TRUE if the 1st character of the
supplied expression is a number, FALSE if
not.
ISLOWER(char expr) Evaluates to TRUE if the 1st character of the
supplied expression is a lower case letter of
the alphabet, FALSE if not.
ISUPPER(char expr) Evaluates to TRUE if the 1st character of the
supplied expression is an upper case letter
of the alphabet, FALSE if not.
LEFT(char expr,numeric expr)Returns the numeric expr characters from
the left of the supplied string.
LEN(char expr) Returns the length of the supplied character
expression.
LOWER(char expr) Returns a string where all letters of the
supplied expression have been converted to
lower case.
LTRIM(char expr) Returns a string which is the supplied string
with all leading spaces removed.
MAX(expr,expr) Returns the greater of the two supplied
numbers or dates.
MIN(expr,expr) Returns the lesser of the two supplied
numbers or dates.
MOD(numeric expr,numeric expr) Gives the remainder when the first
expression is divided by the second.
MONTH(date expr) Returns the number of the month represented
by the supplied date expression.
RECCOUNT() Gives the number of records in the database.
RECNO() Gives the record number (in the dbf file)of
the record on which the function is
evaluated.
RECSIZE() Gives the length of the records in the
database.
REPLICATE(char expr,num expr) Returns a string made by num expr
repeats of the supplied character
expression.
RIGHT(char expr, num expr) Returns the numeric expr characters from
the left of the supplied string.
ROUND(num expr,num dec) Gives the result of rounding off the supplied
numeric expression to dec decimal places.
RTRIM(char expr) (Same as TRIM).
SOUNDEX(char expr) Gives the string of lenght 4 which results
from running the SOUNDEX algorithm on the
supplied character expression. Note that this
is a dBase IV function and is not supported
in dBase III.
SPACE(num expr) Returns a string consisting of num expr space
characters.
STR(number, len, decimal) Returns the string which represents the
number supplied. len is the total length of
the resulting string, decimal is the number
of decimal places.
STUFF(str 1,start,len,str 2)
Gives a sting which is created by taking
str 1, removing the substring which is
defined by start and length, and inserting
str2 where the substring was removed.
SUBSTR(string,start,length) Gives the string which is a substring of
the supplied string starting at position
start and going on for length characters. If
length is not supplied then it returns the
rest of the string. The string is assumed to
start at position 1.
SWAPDATA(string) This is a PCF function which takes the
supplied string and takes the first ~
character in it exchanging the information
after the ~ for the information before it,
and replacing the ~ by a space. Thus
"SMITH~JOHN" becomes "JOHN SMITH". This is
used in PCF for alphabetical sorting by
surname but printing by proper name. This
function is NOT dBase III compatible.
TIME() Gives the character representation of the
current time.
TRIM(string expr) Gives the string which results from removing
all trailing spaces from the supplied string.
TYPE(expr) Gives a single character string which
represents the result type of expr. (C, N, L,
or D). Note that unlike dBase date
expressions are allowed, and that memo fields
return C, not M like they would in dBase.
UPPER(string expr) Returns a string where all letters of the
supplied expression have been converted to
upper case.
VAL(char expr) Gives the numeric value of the expression
contained in char expr. Note that the
expression is a simple number thus "4.5"
returns the number 4.5, but "9/5" returns the
number 9.
YEAR(date expr) Gives a four digit number which is the year
of the date expression.
Examples
========
PACKDB
======
The following example shows a program which packs a database. That is
it takes an input database and creates an identical output database
which has deleted records removed. The program is invoked using the
command line:
PACKDB INFILE OUTFILE [INDEX]
INFILE is the name of the input database OUTFILE is the output
database which will be created. INDEX is the name of the optional
index file which will be used to order the input file so that the
records are written in this order to the output file.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <database.hpp>
#include <math.h>
main(int argc, char *argv[])
{
char index[128]; // Holds index name if supplied
long i;
if (argc<3) // Error need at least two arguments
{
printf("\nUsage : packdb infile outfile [index]\n");
printf("\n\nDuplicates infile database to outfile database");
printf("\nremoving deleted records.\n\n");
printf("\nindex if supplied will operate on infile to define\n");
printf("\nthe order records are written to outfile\n\n");
exit(1);
}
if (argc==4) strcpy(index,argv[3]); else *index=0; // Copy index
database *dbin=new database(argv[1],index); // Open input database
database *dbout=new database(); // Create new output
database
if (dbin->isvalid()) // Check if input database is valid
{
delete dbin; delete dbout;
printf("\nUnable to open input database, error %d\n\n",dbin-
>isvalid());
exit(1);
}
for(i=1; i<=dbin->getnfield(); i++) // Copy all fields
dbout->addfield(dbin->getfield(i));
if (dbout->write(argv[2])) // Check database successfully written
{
delete dbin; delete dbout;
printf("\nUnable to open output database\n\n");
exit(2);
}
record *reci=new record(*dbin); // Record on old database
record *reco=new record(*dbout); // Record on new database
long recn=1;
printf("\n");
int rv=reci->select(FIRST); // set to 1st record (default
undeleted)
while(!rv)
{
for(i=1; i<=dbin->getnfield(); i++) // Copy all fields
{
if (dbin->getfield(i)->gettype()=='N') // Numeric fields
{
reco->setfield(i,atof(reci->getfield(i)));
}
else reco->setfield(i,reci->getfield(i)); // All other fields
}
reco->write(NEW); // Write new record
rv=reci->select(NEXT); // Next input record
printf("\r%ld",recn++);
}
delete reci;
delete reco;
delete dbin;
delete dbout;
printf("\n\nOperation complete\n");
}
FAMILYS
=======
The second example below shows the use of sub totals. This example is
on a database of families which has been indexed on the field name
SURNAME. The program finds all records which have the SURNAME field
set to the same value in the database and works out the average of
the field called AGE (age in years) and AGEM (age in months) within
the family. It then returns and for every member of the family
updates the avgage field which is the difference between the AGE and
AGEM fields in the record and the average age worked out for the
family.
/*
Demonstration of Subtotals,
Short program to update average age for each family in familys.dbf
The average age is a field in each record which will indicate how
far
away each family member is in age from the average age in that
family
Index expression in familys.ndx is :
UPPER(SURNAME)
Thus all family members will be grouped together
*/
#include <string.h>
#include <database.hpp>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
void main()
{
char ws[128]; // Workspace
database db("familys","familys"); // Open database
record rec(db); // Current record
record first(db); // Used to save
position
int fn=db.getfield("avgage")->getnumber(); // Field no.
avgage field
int sel=rec.select(FIRST); // Select first record
do
{
char surname[128]; // Holds current surname
double avgage=0; // Holds average age
first=rec; // Save position
int ssel=sel; // save sel at current position
int trec=0; // Total no. of records in patrol
strcpy(surname,rec.getfield("surname")); // Copy in patrol
name
while(!sel && !strcmp(surname,rec.getfield("surname"))) // Until
new name
{
avgage+=atof(rec.getfield("age"))+atof(rec.getfield("agem"))/12;
trec++;
sel=rec.select(NEXT); // On to next record
}
avgage/=trec;
while(!ssel && !strcmp(surname,first.getfield("surname"))) //
Round again
{
double age;
age=atof(first.getfield("age"))+atof(first.getfield("agem"))/12;
first.setfield(fn,int(age-avgage));
first.write(); // Write the new record
ssel=first.select(NEXT);
}
printf("\nAverage age of %s is %f",surname,avgage);
}
while(!sel);
}
DBRPT
=====
The following program takes a database and prints a report on it,
printing number and length of records, and type and length of each
file. With the -c option it will print an include file for use in a
C++ program which contains "#define" statements for each field
number.
It takes one argument, the file name with an optional -c to print the
the include file.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <database.hpp>
main(int argc, char *argv[])
{
char *wsp,ws[128]; // Useful work space
int inc=0; // Flags results are to be printed in #include
form
if (argc<2) // Must have at least one parameter
{
printf("Usage : dbrpt filename [-c]\n");
printf(" -c option -> #include form for header files\n");
exit(1);
}
if (argc>2) if (!strcmpl(argv[2],"-c")) inc=1;
strcpy(ws,argv[1]); strupr(ws);
if (wsp=strchr(ws,'.')) *wsp=0; if (wsp=strchr(ws,'\\')) *wsp=0;
// Open database
database *db=new database(argv[1]);
if (db->isvalid()) // Check if failed to open
{
delete db;
printf("\nError on opening database !"); exit(1);
}
// Dump database stats.
if (inc) printf("// Database %s, include file\n\n// ",ws);
else printf("\n");
printf("Database : %s has %ld records of length %d with %d
fields\n",
ws,db->getnrec(),db->getreclen(),db->getnfield());
// Dump field stats for each field
for(int i=1; i<=db->getnfield(); i++)
{
field *fld;
fld=db->getfield(i);
if (inc) printf("\n#define %-10s %3d // ",fld->getname(),i);
else printf("\nField %3d - %-10s ",i,fld->getname());
printf("Type %c, Length %3d, Rdp %d",
fld->gettype(),fld->getlen(),fld->getrdp());
}
printf("\n\n");
delete db;
exit(0);
}
Global Utility Functions
========================
The following are global functions available under the database
library, they may be of use in any database application.
char *getdate(time_t time);
This function obtains a time in the dbf form from an ANSI C time, the
returned string is static and is overwritten by each call to getdate.
Thus the current date can be obtained by:
#include <time.h>
#include <database.hpp>
time_t ntime;
time(&ntime);
char *dp=getdate(ntime);
time_t gettime(char *string);
This function obtains a time in the form used by the ANSI time
functions (e.g. asctime() etc). The string should be in dBase form,
YYYYMMDD.
void gnums(char *date,int &d,int &m,int &y);
This function takes the supplied date and returns the day, month and
year functions in the variables d,m and y. See the first example
shown above.
char *ltrim(char *string);
This function returns a pointer to the first character in string
which is not a space.
char *soundex(char *dest,char *src);
This function runs the soundex algorithm on the string src and places
the 4 character result in dest which is returned by the function.
int strcmpdb(char *s1,char *s2,int len=-1);
Compare strings using the dBase compare format. (i.e. "Bancroft"="B"
returns TRUE, "B"="Bancroft") returns false. len is the maximum
length to be compared, if -1 is supplied then the whole of s1 is
examined.
char *swapdata(char *dest,int schar='~');
This is a PCF function which takes the supplied string and takes the
first ~ (or whichever character is defined by schar) character in it
exchanging the information after the ~ for the information before it,
and replacing the ~ by a space. Thus "SMITH~JOHN" becomes "JOHN
SMITH". This is used in PCF for alphabetical sorting by surname but
printing by proper name. The function returns a pointer to dest.
char *trim(char *string)
char *rtrim(char *string)
These functions which are identical trim all trailing spaces from the
supplied string and return a pointer to it.
Classes
=======
The following are the classes which make up the library , and their
associated member functions and variables.
database::database
void database::database();
Description : This constructor creates a database object with no
fields and no records. The valid flag is set to -1 to
indicate that the database has not yet been written to
disk. Fields may be added to the database using the
database::addfield() function. Before records may be
written or read to or from the database then the
database::write() function is used to establish it on
disk.
E.G. // Create a database : "CLUB" with three fields, name
(character)
// dob (date), and phone (character)
#include <database.hpp>
database db();
db->addfield("NAME",'C',15); // Name field
db->addfield("DOB",'D'); // DOB field
db->addfield("PHONE",'C',11); // Phone Field
db->write("CLUB");
database::database
void database::database(char *name,char *index=0);
Description : This constructor opens an existing database which may
have 0 or more records.
The database constructor requires a file name for the
database, and an optional file name for the dBase
compatible index. If the index file name is supplied as
an empty string, or if the index file cannot be opened,
then each record follows the last in the database file.
If an index file name is supplied then each record
follows the last according to the index file sequence.
After the constructor is called the database::isvalid()
function may be called to determine if the database was
successfully opened. Additional indexes may be added
using the database::addindex() function
The database filename is assumed to have a ".DBF"
extension if none is supplied, and the index filename a
".NDX" extension. If the database has an associated memo
file (.DBT extension) then this file is also opened.
The constructor automatically creates and initialises a
linked list of field objects which describes the fields
of the database.
Should the constructor fail then the database::isvalid()
function will report the error, returing 0 on success.
E.G. #include <database.hpp>
database *db;
db=new database("MEMBERS","NAME");
cout << *db;
database::Accessor Functions
long database::getnrec(void)
unsigned int database::getreclen(void);
char *getindkey(name);
int getindkey(name);
int database::getnfield(void);
field *database::getfield(int n);
void database::getversion(int &major,int &minor);
field *database::getfield(char *name);
int database::isvalid(void);
Description : These are the accessor functions for the database, and
return various parameters about it.
char *database::getindkey(char *name)
This function returns the key on the index which has the
supplied file name. name should not have a .NDX extension.
The key should be copied if any modification is to be made to
it. It returns 0 if no index of that name exists.
int database::getindtype(char *name)
This function returns the type of index for the index which
has the supplied file name. name should not have a .NDX
extension. The returned value is either OPINT or OPSTR
depending on whether it is an integer or string index
expression. It returns 0 if no index of the supplied name
exists.
int database::getnfield(void);
This function returns the number of fields in the database.
long database::getnrec(void)
This function returns the number of records in the database.
unsigned int database::getreclen(void);
This function returns the record length of records in the
database.
void database::getversion(int &major,int &minor);
This function sets major to the major number of the database
library version and minor to the minor number. For instance
version 2.3 will set major to 2, and minor to 3.
int database::isvalid(void);
This function returns a code which represents the state of
the database object. The code takes on the following values:
-2 Creating Database with a memo field, not yet
written to disk
-1 Creating Database, not yet written to disk
0 Database file opened and initialised
successfully.
NOFILE Database file could not be opened.
NOINDEX Index file could not be opened.
NOMEM Memory allocation error on database creation.
E.G. #include <database.hpp>
database db("CLUB","")
printf("Database has :\n");
printf("%d Records\n",db.getnrec());
printf("%d Fields\n",db.getnfields());
database::addfield
int database::addfield(char *name,int type,int length=1,int
rdp=0);
int database::addfield(field *fp);
Description : This function adds a field to a database which is
being created.
The first form of the function adds a defined field. name
is the name of the field, type is the field type -
'C','D','M','L' , or 'N'. Length is defined for character
and numeric fields and is the length of these fields in
the database. The other field types are set automatically
and the length parameter is ignored. The rdp parameter is
only used by numeric fields to set the number of decimal
places in the field.
The second form of the function makes a new field from a
pointer to an existing field from an existing (but
different) database.
The function returns 0 if successful, DUPFIELD if the
name of this field exists in the database already, and
INVFIELD if a different error occurs.
E.G. See the database::database() function.
database::addindex
field *database::addindex(char *filename);
Description : This function adds an index to the list of indexes
which is maintained for each database. When an index is
added it is available for use by records which may select
any index, which then defines the order in which records
are read from the database. Any index attached to a
database is automatically updated as records are modified
or added within the database. The file name is assumed to
have a .NDX extension.
Functions such as the record constructor which require to
identify an index on the database use the name defined in
the database constructor or the database::addindex()
function. Thus the index "NAME.NDX" is referred to as
"NAME" in functions which need to identify an index.
The function returns 0 if the index is successfully
attached to the database, and the error NOINDEX if the
file could not be opened. It returns the error INVIND if
there is an error in the index file or if it is already
attached. Index file checking is limited and will not
spot all types of index file corruption.
E.G. #include <database.hpp>
database *db;
db=new database("MEMBERS","AGE");
db.addindex("AGE");
db.addindex("FUNC");
db.addindex("PHDIG");
record rec(db,"FUNC"); // rec uses the FUNC index
database::buildindex
void database::buildindex(char *expr,char *filename,
void (*progress)(int s,long e)=0,
int order=250);
Description : This function constructs a new index on the database.
expr is the expression that is to be used for the index
file, it must be a valid index expression. filename is
the name of the index file to be written, if a file
exists it will be overwritten. Index file creation may
take some considerable time for a large database. After a
successful creation the index can be attached to the
database using the database::addindex() function if it is
desired to use it immediately.
progress is the optional address of a function which can
show the progress of the index build, and can be used for
example, to show a bar chart of % complete.
The function progress will be called every order records
(as default order=250). The function is called with an
integer s, which is the stage of the build, and a long e,
which is the event.
Stage runs from 1 to 3. When stage is 1, then event will
count from 1 to the N which is the number of records.
When stage is 2 then event will count from 1 to a maximum
value which is calculated as N/2*log(N)/log(2), however it
may be a lot less depending on how well sorted the records
are in the database. When stage is 3 then event will count
from 1 to the number of index clusters, it is usually not
worth displaying event when stage is 3, as it is so fast.
You may find that there are periods in stage 2 of the
build (which is the sorting phase), when event is updated
very slowly (for instance the first update may take a long
time). This is normal.
buildindex returns 0 on success, or the error EXPRERR
if an error occurred in expression evaluation on any
record, or the error NOINDEX if the file could not be
opened.
The filename must not be already attached to the owning
or any other database or the program will almost
certainly crash.
E.G. #include <database.hpp>
void prtbuild(int,long);
database db=database("MEMBERS");
db.buildindex("UPPER(NAME)","NAME",prtbuild);
db.addindex("NAME");
void prtbuild(int stage,long event)
{
printf("\rStage %d, Event %ld",stage,event);
}
database::getfield
field *database::getfield(int n);
field *database::getfield(char *name);
Description : These functions return a pointer to the field class
which is defined either by number n, or by its name. If
the field cannot be found a zero pointer is returned.
E.G. #include <database.hpp>
field *fpt;
database db("LIST","NAME");
fpt=db.getfield("NAME");
printf("\nName field is number : %d",fpt->getnumber());
printf("\nName field is of length : %d",fpt->getlen());
database::subindex
int database::subindex(char *name);
Description : This function removes an index from the list of
indexes which is maintained for each database. The name
is the filename which was used to add the index (from the
database::database() or database::addindex() functions).
The function returns 0 on success, or the error NOINDEX
if the index could not be found. It will return INDINUSE
if a record is using the index and the index will not be
removed.
database::setdateformat
void database::setdateformat(dates newform);
Description : This function sets the date format to be used by
the expression evaluator. If newform is USDATE then
the US date format is used, mm/dd/yyyy, if newform is
UKDATE then the european date format is used :
dd/mm/yyyy.
The default is USDATE.
E.G. db->setdateformat(UKDATE);
database::write
int database::write(char *filename);
Description : This function will write a database which is newly
created to disk. filename is the name of the database
(without the .dbf extension) on disk. After this function
has been executed successfully the database will be valid
(the valid flag will be 0), and records may be written
(and then read) on the database.
It returns 0 on success, or NOFILE if the database is not
newly created or cannot be written to disk.
E.G. See the database::database() constructor function.
database::verifyindex
int database::verifyindex(char *indexname,int depth=1,
void (*progress)(int test,long rec)=0,
int order=100);
Description : This function will run through the entire database
using the supplied index name which should already be
attached to the database, and check that the index matches
the records in the database.
depth controls the thoroughness of the test. If depth is 1
(the default), then about 1000 records are checked per
second (on a 33MHz 486), and the test checks that the
index holds the same number of records as the database.
If depth is 2, then the order of records is checked, if
depth is 3, then every record in the database is found
in the index. Depth 2 and 3 are intended mainly for
debugging, and are too slow for normal use.
progress is the optional address of a function which can
show the progress of the index verify, and can be used for
example, to show a bar chart of % checked.
The function progress will be called every order records
(as default order=100). The function is called with an
integer test, which is the stage of the verify, and a long r,
which is the record number. r runs from 1 to the selected
depth. r runs from 1 to the number of records in the
database for each stage.
verifyindex returns:
0 if the index is OK.
INCOMP if the index appears to end before all records
have been read from the database, OORD if the
records are out of order
RECNOTFND if a record is not found in the index
ERRINTREE if the index tree has an error in it
INVIND if the index is not attached to the database,
or there is an error in the index expression.
E.G. database db("CLUB","NAME");
if (db->verifyindex("NAME)) // On error rebuild index
{
db->subindex("name");
db->buildindex("upper(name)","name");
db->addindex("name");
}
field::Accessor Functions
int field::getlen();
char *field::getname();
int field::getnumber();
int field::getrdp();
char field::gettype();
Description : These functions return various information about the
field object to which they are applied.
int field::getlen();
This returns the length of the field in characters.
char *field::getname();
This returns a pointer to the name of the field.
int field::getnumber();
This returns the number of the field.
int field::getrdp();
This returns the number of characters to the right of the
decimal place in a numeric field.
char field::gettype();
This returns the single character field type which will be
one of C,N,D,L, or M. These have the following meanings :
C Character field
N Numeric field
D Date field
L Logical field
M Memo field
E.G. #include <database.hpp>
field *fpt;
database db("DATA","");
fpt=db.getfield("DATE");
cout << "Date field is number :" << fpt->getnumber();
cout << " and of type " << fpt->gettype();
See Also :
record::record
void record::record(class database &db, char *index=0);
Description : The record constructor is called with a database to
which the record object will belong. The object may then
hold any record from that database, and the extraction
functions may be used to obtain any of the fields from
the record. The selection functions are used to read a
required record into the object.
Note that the constructor reads no information into the
record, the record contents will be invalid until the
select functions are used.
index is the index to be used with this record. If no
indexes are in use, or if NOIND is supplied as the index
parameter then records are read from the database in the
order that they were written (this is fastest). If the
index is supplied as 0 then the first index attached to
the database is used for this record, this will be
attached either by the database constructor, or by the
database::addindex() function. If the index is a name
then this is the one used.
The index name is the filename of the .NDX file without
the .NDX extension so the index file "name.ndx" is
referred to as "name".
If the index cannot be opened then no index is associated
with the record.
E.G. #include <database.hpp>
database db("EQUIP","TITLE"); // Database constructor
db.addindex("AGE"); // Age index is attached
db.addindex("NAME"); // Name index is attached
record rec(db); // Record uses "TITLE" index
record rec1(db,NOIND); // Record with no index
record rec2(db,"AGE"); // Record uses "AGE" index
See Also : record::select(), database::addindex(),
database::database()
record::Accessor Functions
int record::getdelstate();
long record::getrecnum();
char *record::indname();
int record::setdelstate(int state);
Description : These functions return information about the record
object.
int record::getdelstate();
This function returns the delete state of the record either,
either DEL or NOTDEL.
long record::getrecnum();
This function returns the record number of the record object
in the database.
char *record::indname();
This function returns the index name used on this record, or
0 if none.
void record::setdelstate(int state);
This setes the delete state of the record, to deleted
(state==DEL), or not deleted (state==NOTDEL). Note that the
record in the dbf file is not updated until a record::write()
function is executed.
record::eval
void record::eval(char *expr,void *result,int &rtype);
void record::eval(void *result,int &rtype);
Description : These functions evaluate an expression which
is provided in ASCII (e.g. "age<12") and return a result.
The expression is evaluated on the current record and
values for fields in the expression are taken from the
current record.
When the function is called, the expression is tokenised
and stored internally. If the same expression is to be
evaluated more than once, then for the second and
subsequent calls the second form of the expression may be
used. This will result in faster evaluation, however the
tokenised form of the expression is overwritten by any
call to eval by any record, by the record::indchk()
function, by the index building functions (which are
called by database::addindex(), database::buildindex(),
and by adding a record to an indexed database), and by
the record select by expression function.
The function returns 0 if there was an error in the
evaluation in which case the owning database string ers
may be checked for an ASCII representation of the error.
On success the function returns 1.
result points to an area of memory into which is written
the result of the evaluation. rtype represents the type
of the result:
rtype Result Type result points to :
1 (OPINT) Numeric A double
2 (OPSTR) String A string
4 (OPDATE) Date An 8 character string in
dBase data format
8 (OPLOG) Logical Single character string - F
or T.
Memo fields may be used freely in expression, however the
expression evaluator maintains its own memory area which
may overflow if results of string expressions are too
long. Memo fields are stored in a buffer which may be
modified by the evaluator, see the record::getfield
function for a further discussion of this.
E.G. // Print the age in years from the dob field
// in the database (date of birth) and the current date.
// Print for all those whose name begins with "A"
database db("CLUB");
record rec(db);
int sel=rec.select("name\"A\"",FIRST);
while(!sel) // For all records
{
char result[128]; // Holds result
int dum; // Dummy variable
cout << "\n" << (char *)rec.getfield("name") << "is ";
int rv=rec.eval("int((date()-dob/365.25)",result,dum);
if (!rv)
{
cout << "Couldn't calculate result because :";
cout << db.ers;
}
else cout << *(double *)result << " years old";
sel=rec.select("name=\"A\"",NEXT);
}
record::getfield
void record::getfield(int n,int trimflag=1);
void record::getfield(char *name,int trimflag=1);
Description : These functions return a pointer to (a
copy of) the specified field. n is the field number (the
first field is number 1), or the field can be specified
by name. It should be noted that it is much faster to
obtain a field by number than by name as the latter
requres a search of all field names in the current data
base.
Note that the string returned is not guaranteed to be
maintained through further calls to the database library
functions, so a copy should be made if it is required to
maintain the field value through other functions. The
function returns a string of spaces if the field cannot
be found, or if a record has not been read with one of
the select functions.
If trimflag is 1 then results are trimmed, that is
trailing spaces are removed, if it is 0 then the field is
returned as is.
The return values of the function are the internal form
held in dbf files, thus the string will have the
following types:
FIELD TYPE FORMAT
Character Zero terminated string form of field.
Numeric ASCII representation of number, use
atoi() or atof() to obtain
number in integer or floating point form.
Date String form of date : YYYYMMDD, thus
19/11/1965 is held as
19651119. Use the gettime() function to
return
an ANSI time in seconds which may be used
with ANSI C time
functions.
Logical The return value is a single character
which holds T,t,Y,y for TRUE and F, f, N, or n for
FALSE.
Memo Returns a pointer to a buffer which holds
the memo in standard string format, see below.
Memos: Memo fields are held in a buffer which is initially 512
bytes long, but is increased in length as required as
bigger memos are found. Not that there is only one memo
buffer per database, and it is overwritten every time
that a memo field is accessed from any record. Expression
which act on memo fields may also modify this buffer.
Thus it is important that if it is desired to keep the
memo field intact through other database function calls
then the buffer should be copied. The buffer (and
therefore any memo field) has a maximum length of 64k.
Empty memos return a null string.
E.G. see record::select
record::indchk
int record::indchk(char *expr,int &rtype);
Description: This function checks the supplied expression and
returns the length of the result in an index expression
(e.g. trim(name) has a length which is the same as the
length of the name field). If the expression is not valid
for an index (e.g. it's result length exceeds 100
characters, the result is logical, or there is an error
in the expression etc.) then the function returns 0, and
the ers string in the database is set to the error
report. rtype is set to the type of the expression which
takes the same values as the rtype parameter in the
record::eval() function.
E.G. database db("Names","CNAME");
record rec(db);
int rt;
int indval=rec.indchk(exprt,rt);
if (!indval)
printf("\n %s is not a valid index because
%s",exprt,rec.db.ers);
record::seldbf
int record::seldbf(long n);
Description: This function sets the record to record number n in
the dbf file regardless of any index file in current use,
or the state of the record (deleted or not). Note that
the first record is record 1.
It returns 0 on success, or CANTSEL if n is out of range.
After this function the record::select() functions may
give unpredictable results unless the
record::select(..FIRST) function is used.
See Also: record::select()
record::select
int record::select(long n,int type=NOTDEL);
int record::select(int fieldnum,int value,long n,int
type=NOTDEL);
int record::select(int fieldnum,char *value,long n,int
type=NOTDEL);
int record::select(int fieldnum,void *value,int (*cmp)(void
*,void *),long n,int type=NOTDEL);
int record::select(char *expr,long n,int type=NOTDEL);
Description : These functions update the record structure
to contain the selected record from the database. The
first function record::select(n) simply selects a record
without regard to the contents of the record, the others
select record by some criteria. The type parameter may be
supplied as NOTDEL, DEL or ALL, this defines whether
deleted, not deleted, or all records which match the
criteria are retrieved.
The second form selects records where the specified field
number which is assumed to be numeric, matches the
supplied integer value. The third form selects record
where the specified field number matches the supplied
string. The fourth form allows the user to supply a
function which searches for records. The function takes
two pointers, the first will be supplied as a string from
the record (this is the value of the specified field),
the second pointer is to the value supplied to the
function.The function should return 0 if it has found a
valid record.
The final form takes a dBase expression which should
return a logical value (e.g. "age<12") and returns all
records for which the expression returns a True value. If
the expression is supplied as "" then the last expression
which was evaluated is used. (Note that any library
function which uses the expression evaluator will modify
the last expression). If an error occurs in evaluation or
a non-logical value is returned, then this function
returns CANTSEL.
The functions read a record from the database according
to the value supplied in n, and then allows the user to
access the fields in that record using the record
accessor functions. The function returns 0 is it was
successful, and the error CANTSEL if it failed.
If an index is in use then the record order specified in
the index file is used by the select functions.
If n is greater than 0 then it is the record number to be
loaded, note that the first record is record number 1.
The record number n is the nth record which matches the
selection criteria, not the record number in the dbf
file, use the record::seldbf() function is select the nth
record in the dbf file.
If n is less than 0 then it takes the values FIRST, LAST,
NEXT or PREVIOUS. These values have the following
meanings:
FIRST The first record which matches the criteria
is loaded.
LAST The last record is loaded.
NEXT The next record in sequence is loaded.
PREVIOUS The previous record in sequence is loaded.
E.G. #include <database.hpp>
int compfirst(void *,void *);
/*******************************/
/* Print all names in database */
void main()
{
database db("MEMBERS","SERIAL");
record rec(db);
int sel;
sel=rec.select(FIRST);
while (!sel)
{
printf("%s\n",rec.getfield("NAME"));
sel=rec.select(NEXT);
}
/*********************************************/
/* Find all members whose name begins with C */
fieldnum=(db.getfield("NAME"))->number;
int rv=rec.select(fieldnum,"C",compfirst,FIRST);
while(!rv)
{
printf("%s\n",rec.getfield(fieldnum));
rv=rec.select(fieldnum,"C",compfirst,NEXT);
}
}
/***************************************************/
/* This function compares two strings and returns */
/* zero if the first characters are the same */
int compfirst(void *name,void *value)
{
return(*(char *)name!=*(char *)value);
}
See Also : record::getfield()
record::selkey
int record::selkey(char *value,int type=NOTDEL);
int record::selkey(double value,int type=NOTDEL);
int record::selkey();
Description : This function selects the record by the index key
attached to the record. value is the key value to find
and is the result of the index expression on the record
which will be matched to the record to find it in the
database. type is either DEL, NOTDEL or ALL and has the
same meanings as in the record::select() functions.
The final form of the function (record::selkey()) selects
records with the same key as supplied previously.
Selecting by key (when known) is much faster than using
the record::select() functions, although the right index
should be used.
It returns with 0 on success, NOKEY if record key could
not be found and in this case will select the record with
the next highest key, if there is no higher key then it
returns NOKEYHIGHER, and selects the last record by
index.
If there is no index it returns CANTSEL.
The record::select() functions may be used with NEXT and
PREVIOUS parameters after this function is used if
required.
E.G. // Find all records where name starts with B
record rec(*db,"Name"); // Use name index
int sel=rec.selkey("B");
while(!sel)
{
printf("\n%s",rec.getfield("name"));
sel=rec.selkey();
}
record::setfield
int record::setfield(char *name,char *value);
int record::setfield(char *name,double value);
int record::setfield(int fieldnum,char *value);
int record::setfield(int fieldnum,double value);
Description : Update a field in the record. The first two forms use
the field name to set the field, the second two forms use
fieldnum as the number of the field. The various forms of
the function convert the argument types to a string and
write this to the field. Date, Logical, Memo and
Character fields should use the char * (as the 2nd
parameter) form of the function, Numeric fields will use
the double form. Date and Logical field values should be
in dBase format (the getdate() function, described in the
Functions part of this document, is useful here).
Note that the record class is updated, but the database
is not updated until a record::write() function is
executed.
The function returns 0 if the field is set to the value
successfully, and RECNOTSET if an error occurred.
The first field is field number 1 when the numeric version
is used.
Memo Fields: Memo fields must provide a pointer to a buffer which
should remain valid until the record is written. The old
buffer in the memo file is overwritten if the new memo
field will fit in the same space as the old one.
E.G. #include <database.hpp>
// Undelete all deleted records, and
// update a field which indicates the number of
modifications
// to the record, this is field number 17
database db("Names");
int rv=rec.select(FIRST,DEL); // Select 1st deleted
record
while(!rv)
{
rec.setdelstate(NOTDEL);
int nwrites=atoi(rec.getfield(17));
rec.setfield(17,nwrites+1);
rec.write();
rv=rec.select(NEXT,DEL);
}
record::write
int record::write(int type=OVER);
Description : Write an updated record back to the dbf file. type is
either OVER or NEW. If type is OVER then the record is
written back on top of the record which was originally
read, this form is used for updating records. If type is
NEW then the record is added at the end of the dbf file.
The function returns 0 if it was successful, and NOWRUNR
if the application has attempted to overwrite an unread
record, and WRFAIL if the write failed for some other
reason.
The function also updates any indexes attached to the
current database, and it is not until this function is
executed that these indexes are updated.
The function checks to see if any other record classes
attached to the database have the same record selected,
if so the information is copied to these records updating
them.
E.G. See record::setfield()
Appendix 1 - Format of database files
DBase_3,_File_Format
DBF File
The DBF file holds all the records in the database together with
global information on fields and records. It consists of 3 parts :
1) The file header which is 32 bytes long.
2) The field definitions each of which is 32 bytes long, the
last field is followed by the byte 0DH.
3) The records each of which follows the last.
File Header
This is 32 bytes long, the contents are as follows :
Byte 0 Bits 0-2 Version of dBase, Bit 7 flags Memo file for
this database if set
Bytes 1-3 Year, Month and Day (in hex) of last modification to
database.
Bytes 4-7 Number of records.
Bytes 7-8 The offset of the first record from the start of the
file.
Bytes 10-11 The record length including the start byte.
Field Definitions
Each definition is 32 bytes long, the contents of each field are
defined as follows:
Bytes 0-10 Zero terminated, upper case field name, max 10
characters.
Byte 11 Type of the field, C, N, L, D, or M.
Byte 16 The length of the field in characters as seen on
screen.
Byte 17 The number of digits right of decimal point for
numeric fields, 0 for all others.
Records
Each record is stored in order of the field contents. The first byte
of each record is either a space ' ' for an undeleted record, or a
'*' for a deleted record. Formats for all data stored in the record
is ASCII except for date and memo fields :
Date These fields are stored in 8 digit ascii, year then
month then day to enable simple sorting. eg 27th
September 1991 is stored as '19910927'.
Memo These fields are stored as 10 ascii digits which show
the cluster number of the memo in the dbt file. Thus
a value of '0000000023' is at location 23*512 in the
memo file. This field is all 0's for no memo on this
record.
NDX, Index Files
These files store the indexes for the database. They consist of a
header cluster, and a complex hierarchical index structure which is
based on 512 byte clusters.
Header
The header consists of the first cluster of the index file (512
bytes). It contains information about the index file.
Bytes 0-3 Cluster number of the first cluster of the index file
which is at the top of the tree.
Bytes 4-7 Number of clusters in the file.
Byte 9 Type of result, D,C or N, Logical index expressions
are invalid.
Bytes 12-13 The size of the result of the dBase index expression
used for this file, in bytes.
Bytes 14-15 The maximum number of index records per cluster.
Bytes 16-17 The result type, 0=character, 1=numeric.
Bytes 18-19 The total length of the index expression plus its
pointers. ie the value in bytes 12-13 + 8+n, where n
is chosen to round up to the nearest 4 bytes.
Byte 24+ The dBase index expression used on this file, which
is zero terminated.
Index Cluster Format
Each cluster consists of a number of records. Each record starts with
two 4 byte numbers, the block number and the record number. Following
this is the record itself which is the result of the dBase index
expression. The cluster starts with the number of records in it, or
the number of records-1 for block clusters, this is also a 4 byte
number.
There are two types of cluster :
A record cluster contains index records where the block number is set
to 0, and the record number is set to the record number in the dbf
file for this index. Following the last record is a 00000000 byte
sequence.
The second type is a block cluster which pointes in a tree like
fashion to another block cluster, or to a record cluster. This type
has as its records the value of the index record of the last item in
the block to which it points. A block cluster has the record number
set to 0, and the block number set to the cluster number of the block
to which it refers.
Memo Files
Memo files are organised into 512 byte clusters. Each memo takes up
an integral number of clusters, and the text ends in the double byte
1A, 1A. The first 4 bytes of the file hold the number of the next
free block, and the rest of block 0 is free for user supplied
comments. Block 1 is the first block available for use as a memo.